package dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
import blogLib.itf.IArtigo;
import blogLib.itf.Visibilidade;
import java.sql.Types;
import java.util.Calendar;
import model.Artigo;
import utils.UtilString;

/**
 * Classe de tradução de dados de objeto para modelo relacional.
 * @author Alan Alves Oliveira
 */
public class ArtigoDAO extends ConexaoBD {
    /**
     * Ordenação pelo Id.
     */
    public static final int ORDENACAO_ID = 0;
    /**
     * Ordenação pela data de postagem.
     */
    public static final int ORDENACAO_DATA = 1;
    /**
     * Ordenação pelo título.
     */
    public static final int ORDENACAO_TITULO = 2;
    private Connection conn;

    /**
     * Construtor que gera uma conexão.
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws SQLException
     */
    public ArtigoDAO(String caminho, String nomeBD, String usuario, String senha) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SQLException {
        this.conn = getConexao(caminho, nomeBD, usuario, senha);
    }

    /**
     * Construtor que recebe uma conexão.
     * @param conn Conexão com banco de dados.
     */
    public ArtigoDAO(Connection conn) {
        this.conn = conn;
    }

    /**
     * Retorna um artigo pelo id.
     * @param id Identificador do artigo a ser retornado.
     * @return Objeto IArtigo. Se o artigo não existir, retorna null.
     * @throws SQLException
     */
    public IArtigo getArtigo(int id) throws SQLException {
        ResultSet rs = null;
        PreparedStatement pst = null;
        IArtigo a = null;
        try {
            pst = conn.prepareStatement("SELECT * FROM artigo WHERE id = ?");
            pst.setInt(1, id);
            rs = pst.executeQuery();
            if (rs.next())
                a = getArtigoFromSQL(rs);
            return a;
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de artigos.
     * @param ordenacao Ordenação da lista de retorno. Deve corresponder aos campos constantes ORDENACAO_ID, ORDENACAO_DATA ou ORDENACAO_TITULO.
     * @return Objeto List com uma lista de artigos.
     * @throws SQLException
     */
    public List<IArtigo> getListaArtigo(int ordenacao) throws SQLException {
        ResultSet rs = null;
        Statement st = null;
        try {
            st = conn.createStatement();
            rs = st.executeQuery("SELECT * FROM artigo" + retornaOrdenacao(ordenacao));
            return montaLista(rs);
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            st.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de artigos filtrando pelo blog ao qual pertencem.
     * @param idBlog Id do blog.
     * @param ordenacao Ordenação da lista de retorno. Deve corresponder aos campos constantes ORDENACAO_ID, ORDENACAO_DATA ou ORDENACAO_TITULO.
     * @return Objeto List com uma lista de artigos.
     * @throws SQLException
     */
    public List<IArtigo> getListaArtigo(int idBlog, int ordenacao) throws SQLException {
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT * FROM artigo WHERE BlogId = ?" + retornaOrdenacao(ordenacao));
            pst.setInt(1, idBlog);
            rs = pst.executeQuery();
            return montaLista(rs);
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de artigos filtrando pelo usuário proprietário.
     * @param loginUsuario Login do usuário.
     * @param ordenacao Ordenação da lista de retorno. Deve corresponder aos campos constantes ORDENACAO_ID, ORDENACAO_DATA ou ORDENACAO_TITULO.
     * @return Objeto List com uma lista de artigos.
     * @throws SQLException
     */
    public List<IArtigo> getListaArtigo(String loginUsuario, int ordenacao) throws SQLException {
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT a.* FROM artigo a" +
                    " INNER JOIN blog b ON a.BlogId = b.Id" +
                    " WHERE b.UsuarioLogin = ?" + retornaOrdenacao(ordenacao));
            pst.setString(1, loginUsuario);
            rs = pst.executeQuery();
            return montaLista(rs);
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de artigos filtrando pelo blog ao qual pertencem, pelo ano e pelo mês.
     * @param idBlog Id do blog.
     * @param ano Ano a ser pesquisado.
     * @param mes Mês a ser pesquisado.
     * @param ordenacao Ordenação da lista de retorno. Deve corresponder aos campos constantes ORDENACAO_ID, ORDENACAO_DATA ou ORDENACAO_TITULO.
     * @return Objeto List com uma lista de artigos.
     * @throws SQLException
     */
    public List<IArtigo> getListaArtigo(int idBlog, int ano, int mes, int ordenacao) throws SQLException {
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT * FROM artigo WHERE BlogId = ?" +
                    " AND YEAR(DataPostagem) = ?" +
                    " AND MONTH(DataPostagem) = ?" + retornaOrdenacao(ordenacao));
            pst.setInt(1, idBlog);
            pst.setInt(2, ano);
            pst.setInt(3, mes);
            rs = pst.executeQuery();
            return montaLista(rs);
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de artigos filtrando pela palavra chave.
     * @param palavraChave Palavra chave a ser pesquisada.
     * @param ordenacao Ordenação da lista de retorno. Deve corresponder aos campos constantes ORDENACAO_ID, ORDENACAO_DATA ou ORDENACAO_TITULO.
     * @return Objeto List com uma lista de artigos.
     * @throws SQLException
     */
    public List<IArtigo> getListaArtigoPalavraChave(String palavraChave, int ordenacao) throws SQLException {
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT * FROM artigo WHERE PalavraChave LIKE ?" + retornaOrdenacao(ordenacao));
            pst.setString(1, "%" + palavraChave + "%");
            rs = pst.executeQuery();
            return montaLista(rs);
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de anos (objetos Integer) de artigos por blog.
     * @param idBlog Id do blog.
     * @return Objeto List com todos os anos que contém algum artigo do blog. A ordenação é por ano em ordem descendente.
     * @throws SQLException
     */
    public List getListaAno(int idBlog) throws SQLException {
        List<Integer> anos = new LinkedList<Integer>();
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT YEAR(DataPostagem) Ano FROM artigo" +
                    " WHERE BlogId = ?" +
                    " GROUP BY YEAR(DataPostagem)" +
                    " ORDER BY YEAR(DataPostagem) DESC");
            pst.setInt(1, idBlog);
            rs = pst.executeQuery();
            while (rs.next())
                anos.add(rs.getInt("Ano"));
            return anos;
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Retorna uma lista de meses (objetos Integer) de artigos por blog e por ano.
     * @param idBlog Id do blog.
     * @param ano Ano a ser pesquisado.
     * @return Objeto List com todos os meses que contém algum artigo do blog no ano pesquisado. A ordenação é por mês em ordem descendente.
     * @throws SQLException
     */
    public List getListaMes(int idBlog, int ano) throws SQLException {
        List<Integer> meses = new LinkedList<Integer>();
        ResultSet rs = null;
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("SELECT MONTH(DataPostagem) Mes FROM artigo" +
                    " WHERE BlogId = ? AND YEAR(DataPostagem) = ?" +
                    " GROUP BY MONTH(DataPostagem)" +
                    " ORDER BY MONTH(DataPostagem) DESC");
            pst.setInt(1, idBlog);
            pst.setInt(2, ano);
            rs = pst.executeQuery();
            while (rs.next())
                meses.add(rs.getInt("Mes"));
            return meses;
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
            rs.close();
        }
    }

    /**
     * Faz a persistência de um artigo no banco de dados.
     * @param a Objeto IArtigo.
     * @return Retorna o ID do artigo no banco de dados.
     * @throws SQLException
     */
    public int incluiArtigo(IArtigo a) throws SQLException {
        Statement st = null;
        ResultSet rs = null;
        String sql = "";
        Calendar dataPostagem = null;
        String textoDataPostagem = null;
        int codigo = -1;
        try {
            dataPostagem = Calendar.getInstance();
            textoDataPostagem = dataPostagem.get(Calendar.YEAR) + "-" +
                    (dataPostagem.get(Calendar.MONTH) + 1) + "-" +
                    dataPostagem.get(Calendar.DATE) + " " +
                    dataPostagem.get(Calendar.HOUR_OF_DAY) + ":" +
                    dataPostagem.get(Calendar.MINUTE) + ":" +
                    dataPostagem.get(Calendar.SECOND);
            sql = "INSERT INTO artigo VALUES (" +
                    "0" +
                    ", " + UtilidadeSQL.verificaNulo(a.getIdBlog(), Types.INTEGER) +
                    ", " + UtilidadeSQL.verificaNulo(a.getTitulo(), Types.VARCHAR) +
                    ", " + UtilidadeSQL.verificaNulo(a.getTexto(), Types.VARCHAR) +
                    ", " + UtilidadeSQL.verificaNulo(a.getPalavraChave(), Types.VARCHAR) +
                    ", " + a.getVisibilidade().getCodigo() +
                    ", '" + textoDataPostagem + "')";
            st = conn.createStatement();
            st.executeUpdate(sql, st.RETURN_GENERATED_KEYS);
            rs = st.getGeneratedKeys();
            if (rs.next())
                codigo = rs.getInt(1);
            return codigo;
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            st.close();
        }

    }

    /**
     * Altera um artigo persistido. Somente os atributos titulo, palavraChave, texto e visibilidade podem ser alterados
     * por este método.
     * @param a Objeto IArtigo.
     * @throws SQLException
     */
    public void alteraArtigo(IArtigo a) throws SQLException {
        PreparedStatement pst = null;
        try {
            pst = conn.prepareStatement("UPDATE artigo SET" +
                " Titulo = ?," +
                " PalavraChave = ?," +
                " Texto = ?," +
                " Visibilidade = ?" +
                " WHERE Id = ?");
            UtilidadeSQL.verificaNulo(a.getTitulo(), Types.VARCHAR, pst, 1);
            UtilidadeSQL.verificaNulo(a.getPalavraChave(), Types.VARCHAR, pst, 2);
            UtilidadeSQL.verificaNulo(a.getTexto(), Types.VARCHAR, pst, 3);
            pst.setInt(4, a.getVisibilidade().getCodigo());
            pst.setInt(5, a.getId());
            pst.execute();
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
        }
    }

    /**
     * Exclui um artigo persistido.
     * @param id Id do artigo.
     * @return Artigo excluído. Se o artigo não existir, retorna null.
     * @throws SQLException
     */
    public IArtigo excluiArtigo(int id) throws SQLException {
        PreparedStatement pst = null;
        try {
            IArtigo a = this.getArtigo(id);
            if (a != null) {
                pst = conn.prepareStatement("DELETE FROM artigo WHERE Id = ?");
                pst.setInt(1, id);
                pst.execute();
            }
            return a;
        } catch (SQLException e) {
            throw new SQLException(e.getMessage());
        } finally {
            pst.close();
        }
    }

    /**
     * Instancia um objeto IArtigo a partir do resultado em um ResultSet.
     * @param rs Objeto ResultSet que contém os dados do artigo.
     * @return Objeto IArtigo.
     * @throws SQLException
     */
    private IArtigo getArtigoFromSQL(ResultSet rs) throws SQLException {
        IArtigo a = new Artigo();
        a.setId(rs.getInt("Id"));
        a.setIdBlog(rs.getInt("BlogId"));
        a.setTitulo(rs.getString("Titulo"));
        a.setTexto(rs.getString("Texto"));
        a.setPalavraChave(UtilString.isnull(rs.getString("PalavraChave")));
        switch (rs.getInt("Visibilidade")) {
            case (0):
                a.setVisibilidade(Visibilidade.PRIVADO);
                break;
            case (1):
                a.setVisibilidade(Visibilidade.USUARIO_REGISTRADO);
                break;
            default:
                a.setVisibilidade(Visibilidade.PUBLICO);
        }
        Calendar data = Calendar.getInstance();
        Calendar hora = Calendar.getInstance();
        data.setTime(rs.getDate("DataPostagem"));
        hora.setTime(rs.getTime("DataPostagem"));
        data.add(Calendar.HOUR_OF_DAY, hora.get(Calendar.HOUR_OF_DAY));
        data.add(Calendar.MINUTE, hora.get(Calendar.MINUTE));
        data.add(Calendar.SECOND, hora.get(Calendar.SECOND));
        a.setDataPostagem(data);
        return a;
    }

    /**
     * Monta uma lista de artigos com o resultado de um ResultSet.
     * @param rs ResultSet que contém os dados do artigo.
     * @return Retorna um objeto List com uma lista de objetos IArtigo.
     * @throws SQLException
     */
    private List<IArtigo> montaLista(ResultSet rs) throws SQLException {
        List<IArtigo> l = new LinkedList<IArtigo>();
        while (rs.next())
            l.add(getArtigoFromSQL(rs));
        return l;
    }

    /**
     * Retorna uma string que representa a ordenação em SQL de acordo com a constante
     * ordenacao.
     * @param ordenacao Valor referente à ordenação.
     * @return Retorna uma string com a cláusula ORDER BY da SQL.
     */
    private String retornaOrdenacao(int ordenacao) {
        String retorno;
        switch (ordenacao) {
            case ORDENACAO_DATA:
                retorno = " ORDER BY DataPostagem";
                break;
            case ORDENACAO_TITULO:
                retorno = " ORDER BY Titulo";
                break;
            default:
                retorno = " ORDER BY Id";
        }
        return retorno;
    }

}